package org.tokaf.datasearcher;

import java.util.ArrayList;
import java.util.NoSuchElementException;
import java.util.Vector;

import org.tokaf.normalizer.Normalizer;
import org.tokaf.normalizer.SimpleJenaNormalizer;
import org.tokaf.normalizer.SimpleNormalizer;

import com.hp.hpl.jena.query.Query;
import com.hp.hpl.jena.query.QueryExecution;
import com.hp.hpl.jena.query.QueryExecutionFactory;
import com.hp.hpl.jena.query.QueryFactory;
import com.hp.hpl.jena.query.QuerySolution;
import com.hp.hpl.jena.query.ResultSet;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.NodeIterator;
import com.hp.hpl.jena.rdf.model.Property;
import com.hp.hpl.jena.rdf.model.RDFNode;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.rdf.model.ResourceFactory;
import com.hp.hpl.jena.rdf.model.Statement;
import com.hp.hpl.jena.rdf.model.impl.PropertyImpl;
import com.hp.hpl.jena.rdf.model.impl.ResourceImpl;

/**
 * <p>Base class for all class working with Jena. Its subclasses must override
 * initSource method.</p> <p>Copyright (c) 2006</p>
 * @author Alan Eckhardt
 * @version 1.0
 */
public abstract class JenaSearcher extends DataSearcher implements DataModifier {
	public JenaSearcher() {
	}

	Model model;

	ResultSet results;

	QueryExecution qexec;

	QuerySolution soln;

	int searchIndex = -1;

	ArrayList alreadySeen = new ArrayList();

	public JenaSearcher(String db, String query, Normalizer norm) {
		initSource(db);
		initSearch(query);
		this.norm = norm;
	}

	public boolean needsSort() {
		return false;
	}

	public SimpleNormalizer getDefaultNormalizer() {
		return new SimpleJenaNormalizer();
	}

	public void sort(int index) {
		throw new UnsupportedOperationException();
	}

	public int initSource(DataSearcher searcher) {

		long startTime = System.nanoTime();
		if (searcher instanceof JenaSearcher) {
			model = ((JenaSearcher) searcher).model;
			initSourceTime += System.nanoTime() - startTime;
			return 1;
		}

		if (searcher instanceof JenaMemorySearcher) {
			model = ((JenaMemorySearcher) searcher).model;
			initSourceTime += System.nanoTime() - startTime;
			return 1;
		}

		initSourceTime += System.nanoTime() - startTime;
		return -1;
	}

	public int deleteTriple(Object subject, Object predicate, Object object) {
		if (subject instanceof String)
			subject = new ResourceImpl((String) subject);
		if (predicate instanceof String)
			predicate = new PropertyImpl((String) predicate);

		Statement a = model.createStatement((Resource) subject,
				(Property) predicate, object);
		model = model.remove(a);
		return 1;

	}

	public int deleteTriple(Object subject, Object predicate, Object object,
			String dataType) {
		if (subject instanceof String)
			subject = new ResourceImpl((String) subject);

		if (predicate instanceof String)
			predicate = new PropertyImpl((String) predicate);

		Statement a = model.createStatement((Resource) subject,
				(Property) predicate, object.toString(), dataType);
		model = model.remove(a);
		return 1;

	}

	public int addTriple(Object subject, Object predicate, Object object) {
		if (subject instanceof String)
			subject = new ResourceImpl((String) subject);
		if (predicate instanceof String)
			predicate = new PropertyImpl((String) predicate);

		model = model.add((Resource) subject, (Property) predicate, object);
		return 1;

	}

	public int addTriple(Object subject, Object predicate, Object object,
			String dataType) {
		if (subject instanceof String)
			subject = new ResourceImpl((String) subject);
		if (predicate instanceof String)
			predicate = new PropertyImpl((String) predicate);

		model = model.add((Resource) subject, (Property) predicate, object
				.toString(), dataType);
		return 1;
	}

	public int addReifiedTriple(Object subject, Object predicate,
			Object object, Object reifPredicate, Object reifObject) {
		if (subject instanceof String)
			subject = new ResourceImpl((String) subject);

		if (predicate instanceof String)
			predicate = new PropertyImpl((String) predicate);

		if (object instanceof String)
			object = ResourceFactory.createResource((String) object);

		if (reifPredicate instanceof String)
			reifPredicate = new PropertyImpl((String) reifPredicate);

		if (reifObject instanceof String)
			reifObject = ResourceFactory.createResource((String) reifObject);

		String rdf = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
		String bnName = ((Resource) subject).getLocalName() + "_"
				+ ((Resource) predicate).getLocalName() + "_"
				+ ((RDFNode) object).toString();

		ResourceImpl bn = new ResourceImpl("_", bnName);
		model = model.add(bn, new PropertyImpl(rdf + "type"), ResourceFactory
				.createResource(rdf + "Statement"));
		model = model.add(bn, new PropertyImpl(rdf + "subject"),
				(Resource) subject);
		model = model.add(bn, new PropertyImpl(rdf + "predicate"),
				(Property) predicate);
		model = model.add(bn, new PropertyImpl(rdf + "object"),
				(RDFNode) object);
		model = model.add(bn, (Property) reifPredicate, (RDFNode) reifObject);
		return 1;
	}

	public int addReifiedTriple(Object subject, Object predicate,
			Object object, String datatype, Object reifPredicate,
			Object reifObject) {
		if (subject instanceof String)
			subject = new ResourceImpl((String) subject);

		if (predicate instanceof String)
			predicate = new PropertyImpl((String) predicate);

		if (reifPredicate instanceof String)
			reifPredicate = new PropertyImpl((String) reifPredicate);

		if (reifObject instanceof String)
			reifObject = ResourceFactory.createResource((String) reifObject);

		String rdf = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
		String bnName = ((Resource) subject).getLocalName() + "_"
				+ ((Resource) predicate).getLocalName() + "_"
				+ ((RDFNode) object).toString();

		ResourceImpl bn = new ResourceImpl("_", bnName);
		model = model.add(bn, new PropertyImpl(rdf + "type"), ResourceFactory
				.createResource(rdf + "Statement"));
		model = model.add(bn, new PropertyImpl(rdf + "subject"),
				(Resource) subject);
		model = model.add(bn, new PropertyImpl(rdf + "predicate"),
				(Property) predicate);
		model = model.add(bn, new PropertyImpl(rdf + "object"), object
				.toString(), datatype);
		model = model.add(bn, (Property) reifPredicate, (RDFNode) reifObject);
		return 1;
	}

	public int addReifiedTriple(Object subject, Object predicate,
			Object object, Object reifPredicate, Object reifObject,
			String reifDatatype) {
		if (subject instanceof String)
			subject = new ResourceImpl((String) subject);

		if (predicate instanceof String)
			predicate = new PropertyImpl((String) predicate);

		if (object instanceof String)
			object = ResourceFactory.createResource((String) object);

		if (reifPredicate instanceof String)
			reifPredicate = new PropertyImpl((String) reifPredicate);

		String rdf = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
		String bnName = ((Resource) subject).getLocalName() + "_"
				+ ((Resource) predicate).getLocalName() + "_"
				+ ((RDFNode) object).toString();

		ResourceImpl bn = new ResourceImpl("_", bnName);
		model = model.add(bn, new PropertyImpl(rdf + "type"), ResourceFactory
				.createResource(rdf + "Statement"));
		model = model.add(bn, new PropertyImpl(rdf + "subject"),
				(Resource) subject);
		model = model.add(bn, new PropertyImpl(rdf + "predicate"),
				(Property) predicate);
		model = model.add(bn, new PropertyImpl(rdf + "object"),
				(RDFNode) object);
		model = model.add(bn, (Property) reifPredicate, reifObject.toString(),
				reifDatatype);
		return 1;
	}

	public int addReifiedTriple(Object subject, Object predicate,
			Object object, String datatype, Object reifPredicate,
			Object reifObject, String reifDatatype) {
		if (subject instanceof String)
			subject = new ResourceImpl((String) subject);

		if (predicate instanceof String)
			predicate = new PropertyImpl((String) predicate);

		if (reifPredicate instanceof String)
			reifPredicate = new PropertyImpl((String) reifPredicate);

		if (reifObject instanceof String)
			reifObject = ResourceFactory.createResource((String) reifObject);

		String rdf = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
		String bnName = ((Resource) subject).getLocalName() + "_"
				+ ((Resource) predicate).getLocalName() + "_"
				+ ((RDFNode) object).toString();

		ResourceImpl bn = new ResourceImpl("_", bnName);
		model = model.add(bn, new PropertyImpl(rdf + "type"), ResourceFactory
				.createResource(rdf + "Statement"));
		model = model.add(bn, new PropertyImpl(rdf + "subject"),
				(Resource) subject);
		model = model.add(bn, new PropertyImpl(rdf + "predicate"),
				(Property) predicate);
		model = model.add(bn, new PropertyImpl(rdf + "object"), object
				.toString(), datatype);
		model = model.add(bn, (Property) reifPredicate, reifObject.toString(),
				reifDatatype);
		return 1;
	}

	public void finalize() throws Throwable {
		super.finalize();
		if (qexec != null)
			qexec.close();

		if (model != null)
			model.close();
	}

	private int advanceSearch(int count) {
		int i = 0;
		try {
			for (i = 0; i < count && results.hasNext(); i++) {
				QuerySolution tmpSoln = results.nextSolution();
				Vector v = new Vector();
				for (int j = 0; j < results.getResultVars().size(); j++)
					v.add(tmpSoln
							.get(results.getResultVars().get(j).toString()));
				alreadySeen.add(v);
			}
		} catch (Exception ex) {
			return i;
		}
		return i;
	}

	/**
	 * advance
	 * @return int
	 */
	public int advance() {
		if (alreadySeen.size() - 1 > searchIndex) {
			searchIndex++;
			return 1;
		}
		if (advanceSearch(1) == 1) {
			searchIndex++;
			return 1;
		} else
			return -1;
	}

	/**
	 * findObject
	 * @param obj Object
	 * @param position int
	 * @param index int
	 * @return Object
	 */
	public Object findObject(Object obj, int position, int index) {

		String queryString = "select ?x ?p ?y " + "WHERE " + "{ "
				+ obj.toString() + " " + name + " ?y . " + "} "
		// + "ORDER BY DESC(?y) "
		;
		if (index == 1) {
			return name;
		} else if (index == 2) {
			PropertyImpl p = new PropertyImpl(name);
			if (!(obj instanceof Resource))
				obj = new ResourceImpl(obj.toString());
			NodeIterator iter = model.listObjectsOfProperty((Resource) obj, p);
			if (iter.hasNext()) {
				return iter.nextNode();
			}
		}
		Query query = QueryFactory.create(queryString);
		QueryExecution qexecTemp = QueryExecutionFactory.create(query, model);
		ResultSet resultsTemp = qexecTemp.execSelect();
		if (resultsTemp.hasNext()) {
			QuerySolution solnTemp = resultsTemp.nextSolution();
			if (index == 1)
				return solnTemp.get("p");
			if (index == 2)
				return solnTemp.get("y");
			return solnTemp.get("x");
		}
		return null;
	}

	public Object getField(int index) {
		return getField(index, searchIndex);
	}

	public Object getField(int index, int row) {
		if (index == 1)
			return name;
		else if (index == 0)
			return ((Vector) alreadySeen.get(row)).get(index);
		return ((Vector) alreadySeen.get(row)).get(index - 1);
	}

	public Object getField(String name) {
		return soln.get(name);
	}

	public int getPosistion() {
		return searchIndex;
	}

	public void initSearch(String queryString) {

		long startTime = System.nanoTime();
		Query query = QueryFactory.create(queryString);
		qexec = QueryExecutionFactory.create(query, model);
		results = qexec.execSelect();
		if (results.hasNext()) {
			soln = results.nextSolution();

			Vector v = new Vector();
			for (int i = 0; i < results.getResultVars().size(); i++)
				v.add(soln.get(results.getResultVars().get(i).toString()));
			alreadySeen.add(v);
			searchIndex = 0;
		}
		initSearchTime += System.nanoTime() - startTime;
	}

	public boolean hasNext() {
		if (searchIndex == -1)
			return false;
		return !(searchIndex == results.getRowNumber()) && !results.hasNext();
	}

	/**
	 * initSource
	 * @param db String
	 * @return int
	 */
	public abstract int initSource(String db);

	public Object getFieldAtPosition(int index, int position) {
		if (position <= alreadySeen.size())
			return getField(index, position);
		if (advanceSearch(position - alreadySeen.size() + 1) != position
				- alreadySeen.size() + 1)
			return null;
		return getField(index, position);
	}

	public Object next() {
		if (searchIndex < 0 || searchIndex >= alreadySeen.size())
			throw new NoSuchElementException();
		Vector v = ((Vector) alreadySeen.get(searchIndex));
		//	v.add(1, name);
		advance();
		return v;
	}

	public void resetSearch() {
		if (searchIndex != -1)
			searchIndex = 0;
	}
}
